﻿// A grid is a table with enhanced functionatlty
define([
    'Wjs/ui/widgets/foundation/ViewHashContainer',
    'Wjs/ui/widgets/foundation/ViewSingleContainer',
    'Wjs/ui/widgets/foundation/ViewMultipleContainer',
    'Wjs/ui/elements/mixins/SelectorHolder',
    'Wjs/ui/elements/mixins/Selectable'
],
function () {
    'use strict';

    var View = Wjs.getType('View'),
        ViewHashContainer = Wjs.getType('ViewHashContainer'),
        ViewSingleContainer = Wjs.getType('ViewSingleContainer'),
        ViewMultipleContainer = Wjs.getType('ViewMultipleContainer'),
        SelectorHolder = Wjs.getType('SelectorHolder'),
        Selectable = Wjs.getType('Selectable'),
        getGrid,
        getLevel; // Returns the maximum level of the child columns which is needed to generate the correct number of header rows for nested columns

    return Wjs.defineOnce({

        type: 'Grid',

        internal: {

            base: ViewHashContainer,

            mixins: [
                SelectorHolder,
                Wjs.getType('Loadable')
            ],

            onBeforeTypeDefined: function () {

                getGrid = function (child) {

                    return child.up(function (p) {

                        return p instanceof Wjs.getType('Grid');
                    })
                };

                getLevel = function (columns) {
                    var level,
                        maxLevel = 1;

                    if (!columns.length) {

                        return 0;
                    }

                    columns.forEach(function (column) {

                        level = column.getLevel(column.getColumns());

                        if (level > maxLevel) {

                            maxLevel = level;
                        }
                    });

                    return maxLevel;
                };

                // GridColumn
                Wjs.define({

                    type: 'GridColumn',

                    internal: {

                        initialize: function () {

                            this.columns = []; // The nested columns
                        }
                    },

                    setHeader: function (item) {

                        this.header = item; // The widget to be cloned to create the column header

                        return this;
                    },

                    setHeaderText: function (text) {

                        this.header = Wjs.new('Text').setText(text);

                        return this;
                    },

                    setHeaderHtml: function (html) {

                        this.header = Wjs.new('Html').setHtml(html);

                        return this;
                    },

                    // The field to map to the record
                    setField: function (field) {

                        this.field = field;

                        return this;
                    },

                    hide: function () {

                        this.isHidden = true;

                        return this;
                    },

                    setDraw: function (fcn) {

                        this.draw = fcn;

                        return this;
                    },

                    addColumn: function (column) {

                        this.columns.push(column);

                        return this;
                    },

                    getColumns: function () {

                        return this.columns;
                    },

                    getLevel: function () {
                        var columns = this.getColumns();

                        return getLevel(columns) + 1;
                    },

                    setWidth: function (width) {

                        this.width = width;

                        return this;
                    },

                    getWidth: function () {

                        return this.width;
                    },

                    setStyle: function (style) {

                        this.style = style;

                        return this;
                    },

                    // Returns the number of nested feal columns under it
                    getSpan: function () {
                        var columns = this.getColumns(),
                            span = 0;

                        if (columns.length == 0) {

                            return 1;
                        }

                        columns.forEach(function (column) {

                            span += column.getSpan();
                        });

                        return span;
                    },

                    // Creates a cell according to the information of this column
                    createHeaderCell: function () {
                        var cell = Wjs.new('GridHeaderCell'),
                            cloned = this.header.clone(); // Clone the header to reuse the column if the grid is cloned

                        this.populateCell(cell, cloned);

                        return cell;
                    },

                    // Creates a cell according to the information of this column
                    createBodyCell: function (row, record, rowIndex, columnIndex) {
                        var cell = Wjs.new(row.defaultItemType || 'GridBodyCell'),
                            value = record[this.field],
                            display = this.createDisplay(value, record, rowIndex, columnIndex);

                        row.addItem(cell);

                        this.populateCell(cell, display);

                        if (this.style) { // Set the style only for the body cell

                            cell.setStyle(this.style);
                        }

                        if (value && value.selected) {

                            cell.select();
                        }

                        return cell;
                    },

                    populateCell: function (cell, display) {
                        var columnSpan = this.getSpan();

                        cell.setItem(display);

                        if (this.width) {

                            cell.setWidth(this.width);
                        }

                        if (this.isHidden) {

                            cell.hide();
                        }

                        if (columnSpan > 1) {

                            cell.setAttribute('colspan', columnSpan);
                        }

                        cell.setColumn(this); // Set the column of the cell
                    },

                    createDisplay: function (value, record, rowIndex, columnIndex) {
                        var content;

                        // Call the drwa function if provided
                        if (this.draw) {

                            content = this.draw(value, record);

                            return Wjs.new('Html').setHtml(content);
                        }

                        // Handle null and false
                        if (!Wjs.isDefined(value)) {

                            content = 'null';
                        }
                        else if (!Wjs.isString(value)) { // The false value is an object

                            content = value.toString();
                        }
                        else {

                            content = value;
                        }

                        return Wjs.new('Text').setText(content);
                    }
                });

                // TemplateGridColumn
                Wjs.define({

                    type: 'TemplateGridColumn',

                    internal: {

                        base: Wjs.getType('GridColumn')
                    },

                    setTemplate: function (template) {

                        this.template = Wjs.compile(template); // Compile the template if any was provided

                        return this;
                    },

                    createDisplay: function (value, record, rowIndex, columnIndex) {

                        value = this.template(value);

                        return Wjs.new('Html').setHtml(value);
                    }

                });

                // ItemGridColumn
                Wjs.define({

                    type: 'ItemGridColumn',

                    internal: {

                        base: Wjs.getType('GridColumn')
                    },

                    setItem: function (item) {

                        this.item = item;

                        return this;
                    },

                    createDisplay: function (value, record, rowIndex, columnIndex) {
                        var display = this.item.clone();

                        //display.setAttribute('row-index', rowIndex);

                        //if (display.loadData) {

                        //    display.loadData(record);
                        //}

                        //if (display.setValue) {

                        //    display.setValue(value);
                        //}

                        return display;
                    }

                });

                // GridHeaderCell
                Wjs.define({

                    type: 'GridHeaderCell',

                    internal: {

                        base: ViewSingleContainer,

                        initialize: function () {

                            this.setTag('th');

                            this.addClass('wjs-grid-header-cell');
                        }
                    },

                    setColumn: function (column) {

                        this.column = column;

                        return this;
                    }

                });

                // GridHeaderRow
                Wjs.define({

                    type: 'GridHeaderRow',

                    internal: {

                        base: ViewMultipleContainer,

                        initialize: function () {

                            this.setTag('tr');

                            this.addClass('wjs-grid-header-row');
                        }
                    }

                });

                // GridHeader
                Wjs.define({

                    type: 'GridHeader',

                    internal: {

                        base: ViewMultipleContainer,

                        initialize: function () {

                            this.setTag('thead');

                            this.addClass('wjs-grid-header');
                        }
                    },

                    // Builds the header from the column definitions
                    build: function () {
                        var grid = getGrid(this),
                            columns = grid.getColumns(),
                            rowCount = getLevel(columns),
                            i,
                            headerRow;

                        this.clearItems();

                        for (i = 0; i < rowCount; ++i) {

                            headerRow = Wjs.new('GridHeaderRow');

                            grid.findColumns(function (column) { // Get the columns that are in the given row

                                return column.getLevel() == 1;

                            }).forEach(function (column) { // Add a header cell to that row
                                var headerCell = column.createHeaderCell();

                                headerRow.addItem(headerCell);
                            });

                            this.addItem(headerRow);
                        }
                    }

                });

                // GridBodyCell
                Wjs.define({

                    type: 'GridBodyCell',

                    internal: {

                        base: ViewSingleContainer,

                        mixins: [
                            Selectable
                        ],

                        initialize: function () {

                            this.setTag('td');

                            this.addClass('wjs-grid-body-cell');
                        }
                    },

                    setColumn: function (column) {

                        this.column = column;

                        return this;
                    },

                    selectionChangedHandler: function (cfg) {
                        var grid = this.up(function (p) {
                            return p instanceof Wjs.getType('Grid');
                        });

                        if (grid.selectionChangedHandler) {

                            grid.selectionChangedHandler(cfg);
                        }
                    },

                    getContent: function () {

                        return this.getItem().getContent();
                    }

                });

                // GridBodyRow
                Wjs.define({

                    type: 'GridBodyRow',

                    internal: {

                        base: ViewMultipleContainer,

                        mixins: [
                            Selectable
                        ],

                        initialize: function () {

                            this.setTag('tr');

                            this.addClass('wjs-grid-body-row');

                            this.record = {}; // Keep a record (data) attached to each row
                        }

                    },

                    getRecord: function () {

                        return this.record;
                    },

                    getCells: function () {

                        return this.getItems();
                    },

                    getCell: function (index) {

                        return this.getCells()[index];
                    },

                    selectionChangedHandler: function (cfg) {
                        var grid = getGrid(this);

                        if (grid.selectionChangedHandler) {

                            grid.selectionChangedHandler(cfg);
                        }
                    }
                });

                // GridBody
                Wjs.define({

                    type: 'GridBody',

                    internal: {

                        base: ViewMultipleContainer,

                        mixins: [
                            Selectable
                        ],

                        initialize: function () {

                            this.setTag('tbody');

                            this.addClass('wjs-grid-body');
                        }
                    },

                    addRow: function (record, rowIndex) {
                        var grid = getGrid(this),
                            row = Wjs.new(this.defaultItemType || 'GridBodyRow'),
                            rowSelector = grid.getRowSelector(),
                            cellSelector = grid.getCellSelector(); // The grid might have both row and cell selectors

                        row.onItemAdded(function (cell) {

                            if (grid.cellAddedHandler) {

                                grid.cellAddedHandler(cell);
                            }

                        });

                        row.record = record; // Set the record

                        if (rowSelector) {

                            row.setSelector(rowSelector);
                        }

                        this.addItem(row); // Add the row before adding the cells so that the onItemAdded function can work for cells

                        grid.findColumns(function (column) {

                            return column.getColumns().length == 0; // Leaf columns
                        })
                        .forEach(function (column, columnIndex) {
                            var cell = column.createBodyCell(row, record, rowIndex, columnIndex);

                            if (cellSelector) {

                                cell.setSelector(cellSelector);
                            }
                        },
                        this);

                        if (record.selected) {

                            row.select();
                        }

                    },

                    findRecords: function (fcn) {
                        var records = [];

                        this.children.forEach(function (child) {
                            var record = child.getRecord();

                            if (fcn(record) === true) {

                                records.push(record);
                            }
                        },
                        this);

                        return records;
                    }
                });

                // GridFooter
                Wjs.define({

                    type: 'GridFooter',

                    internal: {

                        base: ViewMultipleContainer,

                        initialize: function () {

                            this.setTag('footer');

                            this.addClass('wjs-grid-footer');
                        }
                    }

                });

            },

            initialize: function () {
                var me = this;

                me.setTag('table');

                me.addClass('wjs-grid');

                me.addSection('caption',

                    Wjs.new('ViewSingleContainer')

                        .setTag('caption')

                        .addClass('wjs-grid-caption')
                );

                // The column definitions
                me.columns = [];

                me._createHeader = true;

                me.addSection('header',

                    Wjs.new('GridHeader')
                );

                me.onItemAdded(function (row) {

                    if (me.rowAddedHandler) {

                        me.rowAddedHandler(row);
                    }
                });

                me.addSection('body',

                    Wjs.new('GridBody')
                );

                me.addSection('footer',

                    Wjs.new('GridFooter')
                );

            }
        },

        createHeader: function(flag) {

            this._createHeader = flag;

            return this;
        },

        onRowAdded: function(fcn) {

            this.rowAddedHandler = Wjs.chain(this.rowAddedHandler, fcn);

            return this;
        },

        onCellAdded: function(fcn) {

            this.cellAddedHandler = Wjs.chain(this.cellAddedHandler, fcn);

            return this;
        },

        setCaptionItem: function (item) {

            this.getCaption()

                .setItem(item);

            return this;
        },

        setCaptionText: function (text) {

            this.getCaption()

                .setItem(Wjs.new('Text').setText(text));

            return this;
        },

        setCaptionHtml: function (html) {

            this.getCaption()

                .setItem(Wjs.new('Html').setHtml(html));

            return this;
        },

        getCaption: function () {

            return this.getSection('caption');
        },

        getHeader: function () {

            return this.getSection('header');
        },

        getBody: function () {

            return this.getSection('body');
        },

        getFooter: function () {

            return this.getSection('footer');
        },

        setRowSelector: function (selector) {
            var me = this;

            selector.onSelectionChanged(function (cfg) {

                if (me.selectionChangedHandler) {

                    me.selectionChangedHandler(cfg);
                }
            });

            me.rowSelector = selector;

            return me;
        },

        getRowSelector: function () {

            return this.rowSelector;
        },

        setCellSelector: function (selector) {
            var me = this;

            selector.onSelectionChanged(function (cfg) {

                if (me.selectionChangedHandler) {

                    me.selectionChangedHandler(cfg);
                }
            });

            me.cellSelector = selector;

            return me;
        },

        getCellSelector: function () {

            return this.cellSelector;
        },

        addColumn: function (column) {

            this.columns.push(column);

            return this;
        },

        getColumns: function () {

            return this.columns;
        },

        // Recursively returns all the columns of this grid that match the criteria of the test function
        findColumns: function (test, output) {
            var done = false;

            if (!output) { // Assume parent will not provide the output array

                output = [];

                done = true;
            }

            this.getColumns().forEach(function (item) {

                if (!test || test(item)) {

                    output.push(item);
                }

                if (item.findColumns) { // Recurse

                    item.findColumns(test, output);
                }
            },
            this);

            if (done) {

                return output;
            }
        },

        beforeRenderHandler: function () {

            if (this._createHeader) {

                this.getHeader().build(); // Build the header from the columns
            }        
        },

        beforeLoadHandler: function () {

            this.getBody().clearItems();
        },

        loadHandler: function (data, contentType) {

            data.forEach(function (record, rowIndex) {

                this.addRow(record, rowIndex);
            },
            this);
        },

        addRow: function (record, rowIndex) {

            this.getBody().addRow(record, rowIndex);

            return this;
        },

        getRows: function () {

            return this.getBody().getItems();
        },

        getRow: function (i) {

            return this.getBody().items[i];
        },

        // Needed to check whether to test if the container has items, for example, to select the first item in a combo box
        getItems: function () {

            return this.getRows();
        },

        getSelector: function () {

            return this.rowSelector || this.cellSelector;
        },

        // Creates a copy of this element
        clone: function () {
            var cloned = View.prototype.clone.call(this);

            cloned.rowAddedHandler = this.rowAddedHandler;

            cloned.cellAddedHandler = this.cellAddedHandler;

            cloned._createHeader = this._createHeader;

            //cloned.setCaptionItem(this.getCaption().getItem().clone()); TODO: Implement

            //cloned.setRowSelector(this.getRowSelector().clone()); TODO: Implement

            //cloned.setCellSelector(this.getCellSelector().clone()); TODO: Implement

            cloned.columns = this.columns;

            return cloned;
        }

    });

});